package market

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"strconv"
	"strings"
	"sync"
	"time"
)

// FundingRateCache 资金费率缓存结构
// Binance Funding Rate 每 8 小时才更新一次，使用 1 小时缓存可显著减少 API 调用
type FundingRateCache struct {
	Rate      float64
	UpdatedAt time.Time
}

var (
	fundingRateMap sync.Map // map[string]*FundingRateCache
	frCacheTTL     = 1 * time.Hour
)

const (
	DailyInterval    = "1d"
	DailyDataPoints  = 7
	PrimaryTimeframe = "5m"
)

// Get 获取指定代币的市场数据
func Get(symbol string) (*Data, error) {
	var klines5m, klines15m, klines1h, klines4h []Kline
	var err error
	// 标准化symbol
	symbol = Normalize(symbol)
	// 获取5分钟K线数据 (最近10个)
	klines5m, err = WSMonitorCli.GetCurrentKlines(symbol, PrimaryTimeframe) // 多获取一些用于计算
	if err != nil {
		return nil, fmt.Errorf("获取5分钟K线失败: %v", err)
	}

	// Data staleness detection: Prevent DOGEUSDT-style price freeze issues
	if isStaleData(klines5m, symbol) {
		log.Printf("⚠️  WARNING: %s detected stale data (consecutive price freeze), skipping symbol", symbol)
		return nil, fmt.Errorf("%s data is stale, possible cache failure", symbol)
	}

	// 获取15分钟K线数据 (最近10个)
	klines15m, err = WSMonitorCli.GetCurrentKlines(symbol, "15m")
	if err != nil {
		return nil, fmt.Errorf("获取15分钟K线失败: %v", err)
	}

	// 获取1小时K线数据 (最近10个)
	klines1h, err = WSMonitorCli.GetCurrentKlines(symbol, "1h")
	if err != nil {
		return nil, fmt.Errorf("获取1小时K线失败: %v", err)
	}

	// 获取4小时K线数据 (最近10个)
	klines4h, err = WSMonitorCli.GetCurrentKlines(symbol, "4h") // 多获取用于计算指标
	if err != nil {
		return nil, fmt.Errorf("获取4小时K线失败: %v", err)
	}

	// 检查数据是否为空
	if len(klines5m) == 0 {
		return nil, fmt.Errorf("5分钟K线数据为空")
	}
	if len(klines4h) == 0 {
		return nil, fmt.Errorf("4小时K线数据为空")
	}
	if len(klines15m) == 0 {
		return nil, fmt.Errorf("15分钟K线数据为空")
	}
	if len(klines1h) == 0 {
		return nil, fmt.Errorf("1小时K线数据为空")
	}

	// 计算当前指标 (基于5分钟最新数据)
	currentPrice := klines5m[len(klines5m)-1].Close
	currentEMA20 := calculateEMA(klines5m, 20)
	currentMACD := calculateMACD(klines5m)
	currentRSI7 := calculateRSI(klines5m, 7)

	// 计算价格变化百分比
	// 1小时价格变化 = 12个5分钟K线前的价格
	priceChange1h := 0.0
	const candlesPerHour = 12 // 60分钟 / 5分钟
	if len(klines5m) >= candlesPerHour+1 {
		price1hAgo := klines5m[len(klines5m)-candlesPerHour-1].Close
		if price1hAgo > 0 {
			priceChange1h = ((currentPrice - price1hAgo) / price1hAgo) * 100
		}
	}

	// 4小时价格变化 = 1个4小时K线前的价格
	priceChange4h := 0.0
	if len(klines4h) >= 2 {
		price4hAgo := klines4h[len(klines4h)-2].Close
		if price4hAgo > 0 {
			priceChange4h = ((currentPrice - price4hAgo) / price4hAgo) * 100
		}
	}

	// 获取日线K线数据用于计算24小时价格变化
	klines1d, err := WSMonitorCli.GetCurrentKlines(symbol, "1d")
	if err != nil {
		log.Printf("获取日线K线失败: %v", err)
	}

	// 24小时价格变化 = 使用日线K线计算
	priceChange24h := 0.0
	if len(klines1d) >= 2 {
		price24hAgo := klines1d[len(klines1d)-2].Close
		if price24hAgo > 0 {
			priceChange24h = ((currentPrice - price24hAgo) / price24hAgo) * 100
		}
	}

	// 获取OI数据
	oiData, err := getOpenInterestData(symbol)
	if err != nil {
		// OI失败不影响整体,使用默认值
		oiData = &OIData{Latest: 0, Average: 0}
	}

	// 获取Funding Rate
	fundingRate, _ := getFundingRate(symbol)

	// 计算日内系列数据
	intradayData := calculateIntradaySeries(klines5m)

	// 计算中期系列数据 - 15分钟
	midTermData15m := calculateMidTermSeries15m(klines15m)

	// 计算中期系列数据 - 1小时
	midTermData1h := calculateMidTermSeries1h(klines1h)

	// 计算长期数据
	longerTermData := calculateLongerTermData(klines4h)

	// 计算布林带（20 period, 2 std）
	bollinger5m := calculateBollingerBands(klines5m, 20, 2)
	bollinger15m := calculateBollingerBands(klines15m, 20, 2)
	bollinger1h := calculateBollingerBands(klines1h, 20, 2)

	// 计算成交量均量（5m / 15m）
	volume5m := calculateVolumeStats(klines5m, 20)
	volume15m := calculateVolumeStats(klines15m, 20)

	currentADX := 0.0
	if intradayData != nil {
		currentADX = intradayData.LatestADX14
	}

	adx15m := 0.0
	if midTermData15m != nil {
		adx15m = midTermData15m.LatestADX14
	}

	adx1h := 0.0
	if midTermData1h != nil {
		adx1h = midTermData1h.LatestADX14
	}

	adx4h := 0.0
	if longerTermData != nil {
		adx4h = longerTermData.LatestADX14
	}

	// 获取日线数据 (用于趋势描述)
	dailyData, err := getDailyData(symbol)
	if err != nil {
		log.Printf("获取日线数据失败: %v", err)
	}

	return &Data{
		Symbol:            symbol,
		CurrentPrice:      currentPrice,
		PriceChange1h:     priceChange1h,
		PriceChange4h:     priceChange4h,
		PriceChange24h:    priceChange24h,
		CurrentEMA20:      currentEMA20,
		CurrentMACD:       currentMACD,
		CurrentRSI7:       currentRSI7,
		CurrentADX14:      currentADX,
		ADX15m:            adx15m,
		ADX1h:             adx1h,
		ADX4h:             adx4h,
		OpenInterest:      oiData,
		FundingRate:       fundingRate,
		IntradaySeries:    intradayData,
		MidTermSeries15m:  midTermData15m,
		MidTermSeries1h:   midTermData1h,
		LongerTermContext: longerTermData,
		Bollinger5m:       bollinger5m,
		Bollinger15m:      bollinger15m,
		Bollinger1h:       bollinger1h,
		Volume5m:          volume5m,
		Volume15m:         volume15m,
		DailyContext:      dailyData,
	}, nil
}

// calculateEMA 计算EMA
func calculateEMA(klines []Kline, period int) float64 {
	if len(klines) < period {
		return 0
	}

	// 计算SMA作为初始EMA
	sum := 0.0
	for i := 0; i < period; i++ {
		sum += klines[i].Close
	}
	ema := sum / float64(period)

	// 计算EMA
	multiplier := 2.0 / float64(period+1)
	for i := period; i < len(klines); i++ {
		ema = (klines[i].Close-ema)*multiplier + ema
	}

	return ema
}

// calculateMACD 计算MACD
func calculateMACD(klines []Kline) float64 {
	if len(klines) < 26 {
		return 0
	}

	// 计算12期和26期EMA
	ema12 := calculateEMA(klines, 12)
	ema26 := calculateEMA(klines, 26)

	// MACD = EMA12 - EMA26
	return ema12 - ema26
}

// calculateRSI 计算RSI
func calculateRSI(klines []Kline, period int) float64 {
	if len(klines) <= period {
		return 0
	}

	gains := 0.0
	losses := 0.0

	// 计算初始平均涨跌幅
	for i := 1; i <= period; i++ {
		change := klines[i].Close - klines[i-1].Close
		if change > 0 {
			gains += change
		} else {
			losses += -change
		}
	}

	avgGain := gains / float64(period)
	avgLoss := losses / float64(period)

	// 使用Wilder平滑方法计算后续RSI
	for i := period + 1; i < len(klines); i++ {
		change := klines[i].Close - klines[i-1].Close
		if change > 0 {
			avgGain = (avgGain*float64(period-1) + change) / float64(period)
			avgLoss = (avgLoss * float64(period-1)) / float64(period)
		} else {
			avgGain = (avgGain * float64(period-1)) / float64(period)
			avgLoss = (avgLoss*float64(period-1) + (-change)) / float64(period)
		}
	}

	if avgLoss == 0 {
		return 100
	}

	rs := avgGain / avgLoss
	rsi := 100 - (100 / (1 + rs))

	return rsi
}

// calculateBollingerBands 计算布林带
func calculateBollingerBands(klines []Kline, period int, multiplier float64) *BollingerBands {
	if len(klines) < period || period <= 0 {
		return nil
	}

	start := len(klines) - period
	sum := 0.0
	values := make([]float64, period)
	for i := 0; i < period; i++ {
		close := klines[start+i].Close
		values[i] = close
		sum += close
	}
	middle := sum / float64(period)
	variance := 0.0
	for _, close := range values {
		variance += math.Pow(close-middle, 2)
	}
	variance /= float64(period)
	stdDev := math.Sqrt(variance)
	upper := middle + multiplier*stdDev
	lower := middle - multiplier*stdDev
	bandwidth := 0.0
	if middle != 0 {
		bandwidth = (upper - lower) / middle
	}
	percentB := 0.0
	denominator := upper - lower
	if denominator != 0 {
		percentB = (klines[len(klines)-1].Close - lower) / denominator
		if percentB < 0 {
			percentB = 0
		} else if percentB > 1 {
			percentB = 1
		}
	}

	return &BollingerBands{
		Upper:     upper,
		Middle:    middle,
		Lower:     lower,
		Bandwidth: bandwidth,
		PercentB:  percentB,
	}
}

// calculateVolumeStats 计算成交量与均量信息
func calculateVolumeStats(klines []Kline, period int) *VolumeStats {
	if len(klines) == 0 {
		return nil
	}

	window := len(klines)
	if period > 0 && period < window {
		window = period
	}
	start := len(klines) - window
	sum := 0.0
	for i := start; i < len(klines); i++ {
		sum += klines[i].Volume
	}
	average := 0.0
	if window > 0 {
		average = sum / float64(window)
	}
	current := klines[len(klines)-1].Volume
	ratio := 0.0
	if average > 0 {
		ratio = current / average
	}

	return &VolumeStats{
		Current: current,
		Average: average,
		Ratio:   ratio,
	}
}

// calculateATR 计算ATR
func calculateATR(klines []Kline, period int) float64 {
	if len(klines) <= period {
		return 0
	}

	trs := make([]float64, len(klines))
	for i := 1; i < len(klines); i++ {
		high := klines[i].High
		low := klines[i].Low
		prevClose := klines[i-1].Close

		tr1 := high - low
		tr2 := math.Abs(high - prevClose)
		tr3 := math.Abs(low - prevClose)

		trs[i] = math.Max(tr1, math.Max(tr2, tr3))
	}

	// 计算初始ATR
	sum := 0.0
	for i := 1; i <= period; i++ {
		sum += trs[i]
	}
	atr := sum / float64(period)

	// Wilder平滑
	for i := period + 1; i < len(klines); i++ {
		atr = (atr*float64(period-1) + trs[i]) / float64(period)
	}

	return atr
}

// calculateADXSeries 计算指定周期的ADX序列（使用Wilder平滑）
func calculateADXSeries(klines []Kline, period int) []float64 {
	n := len(klines)
	if n <= period {
		return nil
	}

	trs := make([]float64, n)
	plusDM := make([]float64, n)
	minusDM := make([]float64, n)

	for i := 1; i < n; i++ {
		upMove := klines[i].High - klines[i-1].High
		downMove := klines[i-1].Low - klines[i].Low

		if upMove > downMove && upMove > 0 {
			plusDM[i] = upMove
		}
		if downMove > upMove && downMove > 0 {
			minusDM[i] = downMove
		}

		tr1 := klines[i].High - klines[i].Low
		tr2 := math.Abs(klines[i].High - klines[i-1].Close)
		tr3 := math.Abs(klines[i].Low - klines[i-1].Close)
		trs[i] = math.Max(tr1, math.Max(tr2, tr3))
	}

	smoothedTR := 0.0
	smoothedPlusDM := 0.0
	smoothedMinusDM := 0.0
	for i := 1; i <= period && i < n; i++ {
		smoothedTR += trs[i]
		smoothedPlusDM += plusDM[i]
		smoothedMinusDM += minusDM[i]
	}

	adxValues := make([]float64, n)
	if smoothedTR == 0 {
		return adxValues
	}

	const epsilon = 1e-9
	dxCount := 0
	dxSum := 0.0
	prevADX := 0.0

	for i := period; i < n; i++ {
		if i > period {
			smoothedTR = smoothedTR - (smoothedTR / float64(period)) + trs[i]
			smoothedPlusDM = smoothedPlusDM - (smoothedPlusDM / float64(period)) + plusDM[i]
			smoothedMinusDM = smoothedMinusDM - (smoothedMinusDM / float64(period)) + minusDM[i]
		}

		if smoothedTR <= epsilon {
			continue
		}

		plusDI := 100 * (smoothedPlusDM / smoothedTR)
		minusDI := 100 * (smoothedMinusDM / smoothedTR)
		sumDI := plusDI + minusDI
		if sumDI <= epsilon {
			continue
		}

		dx := math.Abs(plusDI-minusDI) / sumDI * 100
		dxCount++
		if dxCount <= period {
			dxSum += dx
			if dxCount == period {
				prevADX = dxSum / float64(period)
				adxValues[i] = prevADX
			}
			continue
		}

		prevADX = ((prevADX * float64(period-1)) + dx) / float64(period)
		adxValues[i] = prevADX
	}

	return adxValues
}

// calculateIntradaySeries 计算日内系列数据
func calculateIntradaySeries(klines []Kline) *IntradayData {
	data := &IntradayData{
		MidPrices:   make([]float64, 0, 10),
		EMA20Values: make([]float64, 0, 10),
		MACDValues:  make([]float64, 0, 10),
		RSI7Values:  make([]float64, 0, 10),
		RSI14Values: make([]float64, 0, 10),
		Volume:      make([]float64, 0, 10),
		ADX14Values: make([]float64, 0, 10),
	}

	// 获取最近10个数据点
	start := len(klines) - 10
	if start < 0 {
		start = 0
	}

	adxSeries := calculateADXSeries(klines, 14)

	for i := start; i < len(klines); i++ {
		data.MidPrices = append(data.MidPrices, klines[i].Close)
		data.Volume = append(data.Volume, klines[i].Volume)

		// 计算每个点的EMA20
		if i >= 19 {
			ema20 := calculateEMA(klines[:i+1], 20)
			data.EMA20Values = append(data.EMA20Values, ema20)
		}

		// 计算每个点的MACD
		if i >= 25 {
			macd := calculateMACD(klines[:i+1])
			data.MACDValues = append(data.MACDValues, macd)
		}

		// 计算每个点的RSI
		if i >= 7 {
			rsi7 := calculateRSI(klines[:i+1], 7)
			data.RSI7Values = append(data.RSI7Values, rsi7)
		}
		if i >= 14 {
			rsi14 := calculateRSI(klines[:i+1], 14)
			data.RSI14Values = append(data.RSI14Values, rsi14)
		}

		if len(adxSeries) > 0 && i < len(adxSeries) {
			data.ADX14Values = append(data.ADX14Values, adxSeries[i])
		}
	}

	// 计算5m ATR14
	data.ATR14 = calculateATR(klines, 14)

	if len(adxSeries) > 0 {
		data.LatestADX14 = adxSeries[len(adxSeries)-1]
	}

	return data
}

// calculateMidTermSeries15m 计算15分钟中期系列数据
func calculateMidTermSeries15m(klines []Kline) *MidTermData15m {
	data := &MidTermData15m{
		MidPrices:   make([]float64, 0, 10),
		EMA20Values: make([]float64, 0, 10),
		MACDValues:  make([]float64, 0, 10),
		RSI7Values:  make([]float64, 0, 10),
		RSI14Values: make([]float64, 0, 10),
		Volume:      make([]float64, 0, 10),
		ADX14Values: make([]float64, 0, 10),
	}

	// 获取最近10个数据点
	start := len(klines) - 10
	if start < 0 {
		start = 0
	}

	adxSeries := calculateADXSeries(klines, 14)

	for i := start; i < len(klines); i++ {
		data.MidPrices = append(data.MidPrices, klines[i].Close)
		data.Volume = append(data.Volume, klines[i].Volume)

		// 计算每个点的EMA20
		if i >= 19 {
			ema20 := calculateEMA(klines[:i+1], 20)
			data.EMA20Values = append(data.EMA20Values, ema20)
		}

		// 计算每个点的MACD
		if i >= 25 {
			macd := calculateMACD(klines[:i+1])
			data.MACDValues = append(data.MACDValues, macd)
		}

		// 计算每个点的RSI
		if i >= 7 {
			rsi7 := calculateRSI(klines[:i+1], 7)
			data.RSI7Values = append(data.RSI7Values, rsi7)
		}
		if i >= 14 {
			rsi14 := calculateRSI(klines[:i+1], 14)
			data.RSI14Values = append(data.RSI14Values, rsi14)
		}

		if len(adxSeries) > 0 && i < len(adxSeries) {
			data.ADX14Values = append(data.ADX14Values, adxSeries[i])
		}
	}

	// 计算15m ATR14
	data.ATR14 = calculateATR(klines, 14)

	if len(adxSeries) > 0 {
		data.LatestADX14 = adxSeries[len(adxSeries)-1]
	}

	return data
}

// calculateMidTermSeries1h 计算1小时中期系列数据
func calculateMidTermSeries1h(klines []Kline) *MidTermData1h {
	data := &MidTermData1h{
		MidPrices:   make([]float64, 0, 10),
		EMA20Values: make([]float64, 0, 10),
		MACDValues:  make([]float64, 0, 10),
		RSI7Values:  make([]float64, 0, 10),
		RSI14Values: make([]float64, 0, 10),
		Volume:      make([]float64, 0, 10),
		ADX14Values: make([]float64, 0, 10),
	}

	// 获取最近10个数据点
	start := len(klines) - 10
	if start < 0 {
		start = 0
	}

	adxSeries := calculateADXSeries(klines, 14)

	for i := start; i < len(klines); i++ {
		data.MidPrices = append(data.MidPrices, klines[i].Close)
		data.Volume = append(data.Volume, klines[i].Volume)

		// 计算每个点的EMA20
		if i >= 19 {
			ema20 := calculateEMA(klines[:i+1], 20)
			data.EMA20Values = append(data.EMA20Values, ema20)
		}

		// 计算每个点的MACD
		if i >= 25 {
			macd := calculateMACD(klines[:i+1])
			data.MACDValues = append(data.MACDValues, macd)
		}

		// 计算每个点的RSI
		if i >= 7 {
			rsi7 := calculateRSI(klines[:i+1], 7)
			data.RSI7Values = append(data.RSI7Values, rsi7)
		}
		if i >= 14 {
			rsi14 := calculateRSI(klines[:i+1], 14)
			data.RSI14Values = append(data.RSI14Values, rsi14)
		}

		if len(adxSeries) > 0 && i < len(adxSeries) {
			data.ADX14Values = append(data.ADX14Values, adxSeries[i])
		}
	}

	// 计算1h ATR14
	data.ATR14 = calculateATR(klines, 14)

	if len(adxSeries) > 0 {
		data.LatestADX14 = adxSeries[len(adxSeries)-1]
	}

	return data
}

// calculateLongerTermData 计算长期数据
func calculateLongerTermData(klines []Kline) *LongerTermData {
	data := &LongerTermData{
		MACDValues:  make([]float64, 0, 10),
		RSI14Values: make([]float64, 0, 10),
		ADX14Values: make([]float64, 0, 10),
	}

	// 计算EMA
	data.EMA20 = calculateEMA(klines, 20)
	data.EMA50 = calculateEMA(klines, 50)

	// 计算ATR
	data.ATR3 = calculateATR(klines, 3)
	data.ATR14 = calculateATR(klines, 14)

	// 计算成交量
	if len(klines) > 0 {
		data.CurrentVolume = klines[len(klines)-1].Volume
		// 计算平均成交量
		sum := 0.0
		for _, k := range klines {
			sum += k.Volume
		}
		data.AverageVolume = sum / float64(len(klines))
	}

	// 计算MACD和RSI序列
	start := len(klines) - 10
	if start < 0 {
		start = 0
	}

	adxSeries := calculateADXSeries(klines, 14)

	for i := start; i < len(klines); i++ {
		if i >= 25 {
			macd := calculateMACD(klines[:i+1])
			data.MACDValues = append(data.MACDValues, macd)
		}
		if i >= 14 {
			rsi14 := calculateRSI(klines[:i+1], 14)
			data.RSI14Values = append(data.RSI14Values, rsi14)
		}
		if len(adxSeries) > 0 && i < len(adxSeries) {
			data.ADX14Values = append(data.ADX14Values, adxSeries[i])
		}
	}

	if len(adxSeries) > 0 {
		data.LatestADX14 = adxSeries[len(adxSeries)-1]
	}

	return data
}

// getOpenInterestData 获取OI数据
func getOpenInterestData(symbol string) (*OIData, error) {
	url := fmt.Sprintf("https://fapi.binance.com/fapi/v1/openInterest?symbol=%s", symbol)

	apiClient := NewAPIClient()
	resp, err := apiClient.client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result struct {
		OpenInterest string `json:"openInterest"`
		Symbol       string `json:"symbol"`
		Time         int64  `json:"time"`
	}

	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}

	oi, _ := strconv.ParseFloat(result.OpenInterest, 64)

	return &OIData{
		Latest:  oi,
		Average: oi * 0.999, // 近似平均值
	}, nil
}

// getFundingRate 获取资金费率（优化：使用 1 小时缓存）
func getFundingRate(symbol string) (float64, error) {
	// 检查缓存（有效期 1 小时）
	// Funding Rate 每 8 小时才更新，1 小时缓存非常合理
	if cached, ok := fundingRateMap.Load(symbol); ok {
		cache := cached.(*FundingRateCache)
		if time.Since(cache.UpdatedAt) < frCacheTTL {
			// 缓存命中，直接返回
			return cache.Rate, nil
		}
	}

	// 缓存过期或不存在，调用 API
	url := fmt.Sprintf("https://fapi.binance.com/fapi/v1/premiumIndex?symbol=%s", symbol)

	apiClient := NewAPIClient()
	resp, err := apiClient.client.Get(url)
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, err
	}

	var result struct {
		Symbol          string `json:"symbol"`
		MarkPrice       string `json:"markPrice"`
		IndexPrice      string `json:"indexPrice"`
		LastFundingRate string `json:"lastFundingRate"`
		NextFundingTime int64  `json:"nextFundingTime"`
		InterestRate    string `json:"interestRate"`
		Time            int64  `json:"time"`
	}

	if err := json.Unmarshal(body, &result); err != nil {
		return 0, err
	}

	rate, _ := strconv.ParseFloat(result.LastFundingRate, 64)

	// 更新缓存
	fundingRateMap.Store(symbol, &FundingRateCache{
		Rate:      rate,
		UpdatedAt: time.Now(),
	})

	return rate, nil
}

func getDailyData(symbol string) (*DailyData, error) {
	// 获取全部 klines 用于计算指标
	fullKlines, err := WSMonitorCli.GetCurrentKlines(symbol, DailyInterval)
	if err != nil {
		return nil, err
	}

	// 截取最后 7 根用于显示
	klines := fullKlines
	if len(klines) > DailyDataPoints {
		klines = klines[len(klines)-DailyDataPoints:]
	}

	data := &DailyData{
		Dates:       make([]string, len(klines)),
		OpenPrices:  make([]float64, len(klines)),
		HighPrices:  make([]float64, len(klines)),
		LowPrices:   make([]float64, len(klines)),
		ClosePrices: make([]float64, len(klines)),
		Volume:      make([]float64, len(klines)),
	}

	// 填充数据
	maxHigh := -math.MaxFloat64
	minLow := math.MaxFloat64

	for i, k := range klines {
		data.Dates[i] = time.UnixMilli(k.OpenTime).Format("2006-01-02")
		data.OpenPrices[i] = k.Open
		data.HighPrices[i] = k.High
		data.LowPrices[i] = k.Low
		data.ClosePrices[i] = k.Close
		data.Volume[i] = k.Volume

		if k.High > maxHigh {
			maxHigh = k.High
		}
		if k.Low < minLow {
			minLow = k.Low
		}
	}

	// 计算指标 (基于全部数据)
	data.EMA20Values = make([]float64, len(klines))
	data.EMA50Values = make([]float64, len(klines))
	data.MACDValues = make([]float64, len(klines))
	data.RSI14Values = make([]float64, len(klines))

	// 计算指标需要使用 fullKlines
	// 我们需要对应 klines 中的每个点，计算其在 fullKlines 中的指标值
	startIdx := len(fullKlines) - len(klines)

	for i := 0; i < len(klines); i++ {
		fullIdx := startIdx + i
		if fullIdx >= 19 {
			data.EMA20Values[i] = calculateEMA(fullKlines[:fullIdx+1], 20)
		}
		if fullIdx >= 49 {
			data.EMA50Values[i] = calculateEMA(fullKlines[:fullIdx+1], 50)
		}
		if fullIdx >= 25 {
			data.MACDValues[i] = calculateMACD(fullKlines[:fullIdx+1])
		}
		if fullIdx >= 14 {
			data.RSI14Values[i] = calculateRSI(fullKlines[:fullIdx+1], 14)
		}
	}

	data.ATR14 = calculateATR(fullKlines, 14)

	// 计算关键价位 (基于最近7根)
	data.Recent7High = maxHigh
	data.Recent7Low = minLow

	// 判断趋势
	if len(data.ClosePrices) > 0 {
		lastIdx := len(data.ClosePrices) - 1
		currentPrice := data.ClosePrices[lastIdx]
		ema20 := data.EMA20Values[lastIdx]
		ema50 := data.EMA50Values[lastIdx]

		// 确保 EMA 值有效 (非0)
		if ema20 > 0 && ema50 > 0 {
			if currentPrice > ema20 && ema20 > ema50 {
				data.TrendBias = "bullish"
			} else if currentPrice < ema20 && ema20 < ema50 {
				data.TrendBias = "bearish"
			} else {
				data.TrendBias = "neutral"
			}
		} else {
			data.TrendBias = "neutral" // 数据不足以计算趋势
		}
	}

	return data, nil
}

// Format 格式化市场数据为字符串
// skipSymbolMention: 如果为 true，在描述 OI/Funding 时不提及币种名称（避免重复）
func Format(data *Data, skipSymbolMention bool) string {
	var sb strings.Builder

	// 使用动态精度格式化价格
	priceStr := formatPriceWithDynamicPrecision(data.CurrentPrice)
	sb.WriteString(fmt.Sprintf("current_price = %s, price_change_1h = %.2f%%, price_change_4h = %.2f%%, price_change_24h = %.2f%%\n\n",
		priceStr, data.PriceChange1h, data.PriceChange4h, data.PriceChange24h))
	sb.WriteString(fmt.Sprintf("current_ema20 = %.3f, current_macd = %.3f, current_rsi (7 period) = %.3f\n\n",
		data.CurrentEMA20, data.CurrentMACD, data.CurrentRSI7))

	if data.Bollinger5m != nil || data.Bollinger15m != nil || data.Bollinger1h != nil {
		sb.WriteString("Bollinger snapshot (20-period, 2-sigma):\n")
		if line := formatBollingerLine("5m", data.Bollinger5m); line != "" {
			sb.WriteString(line)
		}
		if line := formatBollingerLine("15m", data.Bollinger15m); line != "" {
			sb.WriteString(line)
		}
		if line := formatBollingerLine("1h", data.Bollinger1h); line != "" {
			sb.WriteString(line)
		}
		sb.WriteString("\n")
	}

	if data.Volume5m != nil || data.Volume15m != nil {
		sb.WriteString("Volume vs moving averages (latest vs 20-period avg):\n")
		if line := formatVolumeLine("5m", data.Volume5m); line != "" {
			sb.WriteString(line)
		}
		if line := formatVolumeLine("15m", data.Volume15m); line != "" {
			sb.WriteString(line)
		}
		sb.WriteString("\n")
	}

	if snapshot := formatADXSnapshotLine(data); snapshot != "" {
		sb.WriteString(snapshot)
		sb.WriteString("\n\n")
	}

	if skipSymbolMention {
		sb.WriteString("Here is the latest open interest and funding rate for perps:\n\n")
	} else {
		sb.WriteString(fmt.Sprintf("In addition, here is the latest %s open interest and funding rate for perps:\n\n",
			data.Symbol))
	}

	if data.OpenInterest != nil {
		// 使用动态精度格式化 OI 数据
		oiLatestStr := formatPriceWithDynamicPrecision(data.OpenInterest.Latest)
		oiAverageStr := formatPriceWithDynamicPrecision(data.OpenInterest.Average)
		sb.WriteString(fmt.Sprintf("Open Interest: Latest: %s Average: %s\n\n",
			oiLatestStr, oiAverageStr))
	}

	sb.WriteString(fmt.Sprintf("Funding Rate: %.2e\n\n", data.FundingRate))

	if data.DailyContext != nil {
		sb.WriteString("Daily context (last 7 days):\n")
		sb.WriteString(fmt.Sprintf("Trend bias: %s\n", data.DailyContext.TrendBias))
		sb.WriteString(fmt.Sprintf("7-day range: %.2f - %.2f\n",
			data.DailyContext.Recent7Low, data.DailyContext.Recent7High))
		if len(data.DailyContext.ClosePrices) > 0 {
			lastIdx := len(data.DailyContext.ClosePrices) - 1
			sb.WriteString(fmt.Sprintf("Current vs EMA20: %.2f vs %.2f\n",
				data.DailyContext.ClosePrices[lastIdx],
				data.DailyContext.EMA20Values[lastIdx]))
			sb.WriteString(fmt.Sprintf("EMA20 vs EMA50: %.2f vs %.2f\n",
				data.DailyContext.EMA20Values[lastIdx],
				data.DailyContext.EMA50Values[lastIdx]))
		}
		sb.WriteString(fmt.Sprintf("Daily ATR (14): %.4f\n\n", data.DailyContext.ATR14))

		// 最近5天的OHLC
		sb.WriteString("Recent 5 days OHLC:\n")
		start := len(data.DailyContext.Dates) - 5
		if start < 0 {
			start = 0
		}
		for i := start; i < len(data.DailyContext.Dates); i++ {
			sb.WriteString(fmt.Sprintf("  %s: O=%.2f H=%.2f L=%.2f C=%.2f\n",
				data.DailyContext.Dates[i],
				data.DailyContext.OpenPrices[i],
				data.DailyContext.HighPrices[i],
				data.DailyContext.LowPrices[i],
				data.DailyContext.ClosePrices[i]))
		}

		// RSI序列（最近10天）
		if len(data.DailyContext.RSI14Values) > 0 {
			startRSI := len(data.DailyContext.RSI14Values) - 10
			if startRSI < 0 {
				startRSI = 0
			}
			sb.WriteString(fmt.Sprintf("Daily RSI14 (last 10): %v\n\n",
				formatFloatSlice(data.DailyContext.RSI14Values[startRSI:])))
		}
	}

	if data.IntradaySeries != nil {
		sb.WriteString("Intraday series (5‑minute intervals, oldest → latest):\n\n")

		if len(data.IntradaySeries.MidPrices) > 0 {
			sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.IntradaySeries.MidPrices)))
		}

		if len(data.IntradaySeries.EMA20Values) > 0 {
			sb.WriteString(fmt.Sprintf("EMA indicators (20‑period): %s\n\n", formatFloatSlice(data.IntradaySeries.EMA20Values)))
		}

		if len(data.IntradaySeries.MACDValues) > 0 {
			sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.IntradaySeries.MACDValues)))
		}

		if len(data.IntradaySeries.RSI7Values) > 0 {
			sb.WriteString(fmt.Sprintf("RSI indicators (7‑Period): %s\n\n", formatFloatSlice(data.IntradaySeries.RSI7Values)))
		}

		if len(data.IntradaySeries.RSI14Values) > 0 {
			sb.WriteString(fmt.Sprintf("RSI indicators (14‑Period): %s\n\n", formatFloatSlice(data.IntradaySeries.RSI14Values)))
		}

		if len(data.IntradaySeries.Volume) > 0 {
			sb.WriteString(fmt.Sprintf("Volume: %s\n\n", formatFloatSlice(data.IntradaySeries.Volume)))
		}

		if len(data.IntradaySeries.ADX14Values) > 0 {
			sb.WriteString(fmt.Sprintf("ADX (14‑period): %s (latest %.2f)\n\n",
				formatIndicatorSlice(data.IntradaySeries.ADX14Values), data.IntradaySeries.LatestADX14))
		}

		sb.WriteString(fmt.Sprintf("5m ATR (14‑period): %.3f\n\n", data.IntradaySeries.ATR14))
	}

	if data.MidTermSeries15m != nil {
		sb.WriteString("Mid‑term series (15‑minute intervals, oldest → latest):\n\n")

		if len(data.MidTermSeries15m.MidPrices) > 0 {
			sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidTermSeries15m.MidPrices)))
		}

		if len(data.MidTermSeries15m.EMA20Values) > 0 {
			sb.WriteString(fmt.Sprintf("EMA indicators (20‑period): %s\n\n", formatFloatSlice(data.MidTermSeries15m.EMA20Values)))
		}

		if len(data.MidTermSeries15m.MACDValues) > 0 {
			sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.MidTermSeries15m.MACDValues)))
		}

		if len(data.MidTermSeries15m.RSI7Values) > 0 {
			sb.WriteString(fmt.Sprintf("RSI indicators (7‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries15m.RSI7Values)))
		}

		if len(data.MidTermSeries15m.RSI14Values) > 0 {
			sb.WriteString(fmt.Sprintf("RSI indicators (14‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries15m.RSI14Values)))
		}

		if len(data.MidTermSeries15m.Volume) > 0 {
			sb.WriteString(fmt.Sprintf("Volume: %s\n\n", formatFloatSlice(data.MidTermSeries15m.Volume)))
		}

		if len(data.MidTermSeries15m.ADX14Values) > 0 {
			sb.WriteString(fmt.Sprintf("ADX (14‑period): %s (latest %.2f)\n\n",
				formatIndicatorSlice(data.MidTermSeries15m.ADX14Values), data.MidTermSeries15m.LatestADX14))
		}

		sb.WriteString(fmt.Sprintf("15m ATR (14‑period): %.3f\n\n", data.MidTermSeries15m.ATR14))
	}

	if data.MidTermSeries1h != nil {
		sb.WriteString("Mid‑term series (1‑hour intervals, oldest → latest):\n\n")

		if len(data.MidTermSeries1h.MidPrices) > 0 {
			sb.WriteString(fmt.Sprintf("Mid prices: %s\n\n", formatFloatSlice(data.MidTermSeries1h.MidPrices)))
		}

		if len(data.MidTermSeries1h.EMA20Values) > 0 {
			sb.WriteString(fmt.Sprintf("EMA indicators (20‑period): %s\n\n", formatFloatSlice(data.MidTermSeries1h.EMA20Values)))
		}

		if len(data.MidTermSeries1h.MACDValues) > 0 {
			sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.MidTermSeries1h.MACDValues)))
		}

		if len(data.MidTermSeries1h.RSI7Values) > 0 {
			sb.WriteString(fmt.Sprintf("RSI indicators (7‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries1h.RSI7Values)))
		}

		if len(data.MidTermSeries1h.RSI14Values) > 0 {
			sb.WriteString(fmt.Sprintf("RSI indicators (14‑Period): %s\n\n", formatFloatSlice(data.MidTermSeries1h.RSI14Values)))
		}

		if len(data.MidTermSeries1h.Volume) > 0 {
			sb.WriteString(fmt.Sprintf("Volume: %s\n\n", formatFloatSlice(data.MidTermSeries1h.Volume)))
		}

		if len(data.MidTermSeries1h.ADX14Values) > 0 {
			sb.WriteString(fmt.Sprintf("ADX (14‑period): %s (latest %.2f)\n\n",
				formatIndicatorSlice(data.MidTermSeries1h.ADX14Values), data.MidTermSeries1h.LatestADX14))
		}

		sb.WriteString(fmt.Sprintf("1h ATR (14‑period): %.3f\n\n", data.MidTermSeries1h.ATR14))
	}

	if data.LongerTermContext != nil {
		sb.WriteString("Longer‑term context (4‑hour timeframe):\n\n")

		sb.WriteString(fmt.Sprintf("20‑Period EMA: %.3f vs. 50‑Period EMA: %.3f\n\n",
			data.LongerTermContext.EMA20, data.LongerTermContext.EMA50))

		sb.WriteString(fmt.Sprintf("3‑Period ATR: %.3f vs. 14‑Period ATR: %.3f\n\n",
			data.LongerTermContext.ATR3, data.LongerTermContext.ATR14))

		sb.WriteString(fmt.Sprintf("Current Volume: %.3f vs. Average Volume: %.3f\n\n",
			data.LongerTermContext.CurrentVolume, data.LongerTermContext.AverageVolume))

		if len(data.LongerTermContext.MACDValues) > 0 {
			sb.WriteString(fmt.Sprintf("MACD indicators: %s\n\n", formatFloatSlice(data.LongerTermContext.MACDValues)))
		}

		if len(data.LongerTermContext.RSI14Values) > 0 {
			sb.WriteString(fmt.Sprintf("RSI indicators (14‑Period): %s\n\n", formatFloatSlice(data.LongerTermContext.RSI14Values)))
		}

		if len(data.LongerTermContext.ADX14Values) > 0 {
			sb.WriteString(fmt.Sprintf("ADX (14‑period): %s (latest %.2f)\n\n",
				formatIndicatorSlice(data.LongerTermContext.ADX14Values), data.LongerTermContext.LatestADX14))
		}
	}

	if data.DailyContext != nil {
		sb.WriteString("\nDaily context (last 7 days):\n\n")

		// 趋势摘要
		sb.WriteString(fmt.Sprintf("Trend bias: %s\n", data.DailyContext.TrendBias))
		sb.WriteString(fmt.Sprintf("7-day range: %.2f - %.2f\n", data.DailyContext.Recent7Low, data.DailyContext.Recent7High))

		if len(data.DailyContext.ClosePrices) > 0 {
			lastIdx := len(data.DailyContext.ClosePrices) - 1
			sb.WriteString(fmt.Sprintf("Current vs EMA20: %.2f vs %.2f\n",
				data.DailyContext.ClosePrices[lastIdx],
				data.DailyContext.EMA20Values[lastIdx]))
			sb.WriteString(fmt.Sprintf("EMA20 vs EMA50: %.2f vs %.2f\n",
				data.DailyContext.EMA20Values[lastIdx],
				data.DailyContext.EMA50Values[lastIdx]))
		}

		sb.WriteString(fmt.Sprintf("Daily ATR (14): %.4f\n\n", data.DailyContext.ATR14))

		// 最近5天的OHLC
		sb.WriteString("Recent 5 days OHLC:\n")
		start := len(data.DailyContext.Dates) - 5
		if start < 0 {
			start = 0
		}
		for i := start; i < len(data.DailyContext.Dates); i++ {
			sb.WriteString(fmt.Sprintf("  %s: O=%.2f H=%.2f L=%.2f C=%.2f\n",
				data.DailyContext.Dates[i],
				data.DailyContext.OpenPrices[i],
				data.DailyContext.HighPrices[i],
				data.DailyContext.LowPrices[i],
				data.DailyContext.ClosePrices[i]))
		}

		// MACD序列（最近10天）
		if len(data.DailyContext.MACDValues) > 0 {
			startMACD := len(data.DailyContext.MACDValues) - 10
			if startMACD < 0 {
				startMACD = 0
			}
			sb.WriteString(fmt.Sprintf("\nDaily MACD (last 10): %v\n",
				formatFloatSlice(data.DailyContext.MACDValues[startMACD:])))
		}

		// RSI序列（最近10天）
		if len(data.DailyContext.RSI14Values) > 0 {
			startRSI := len(data.DailyContext.RSI14Values) - 10
			if startRSI < 0 {
				startRSI = 0
			}
			sb.WriteString(fmt.Sprintf("\nDaily RSI14 (last 10): %v\n",
				formatFloatSlice(data.DailyContext.RSI14Values[startRSI:])))
		}
	}

	return sb.String()
}

// formatPriceWithDynamicPrecision 根据价格区间动态选择精度
// 这样可以完美支持从超低价 meme coin (< 0.0001) 到 BTC/ETH 的所有币种
func formatPriceWithDynamicPrecision(price float64) string {
	switch {
	case price < 0.0001:
		// 超低价 meme coin: 1000SATS, 1000WHY, DOGS
		// 0.00002070 → "0.00002070" (8位小数)
		return fmt.Sprintf("%.8f", price)
	case price < 0.001:
		// 低价 meme coin: NEIRO, HMSTR, HOT, NOT
		// 0.00015060 → "0.000151" (6位小数)
		return fmt.Sprintf("%.6f", price)
	case price < 0.01:
		// 中低价币: PEPE, SHIB, MEME
		// 0.00556800 → "0.005568" (6位小数)
		return fmt.Sprintf("%.6f", price)
	case price < 1.0:
		// 低价币: ASTER, DOGE, ADA, TRX
		// 0.9954 → "0.9954" (4位小数)
		return fmt.Sprintf("%.4f", price)
	case price < 100:
		// 中价币: SOL, AVAX, LINK, MATIC
		// 23.4567 → "23.4567" (4位小数)
		return fmt.Sprintf("%.4f", price)
	default:
		// 高价币: BTC, ETH (节省 Token)
		// 45678.9123 → "45678.91" (2位小数)
		return fmt.Sprintf("%.2f", price)
	}
}

// formatIndicatorSlice 格式化技术指标序列，限制为两位小数便于阅读
func formatIndicatorSlice(values []float64) string {
	if len(values) == 0 {
		return "[]"
	}
	formatted := make([]string, len(values))
	for i, v := range values {
		formatted[i] = fmt.Sprintf("%.2f", v)
	}
	return "[" + strings.Join(formatted, ", ") + "]"
}

// formatBollingerLine 构建布林带描述
func formatBollingerLine(label string, bands *BollingerBands) string {
	if bands == nil {
		return ""
	}
	return fmt.Sprintf("- %s: upper %.4f | middle %.4f | lower %.4f | bandwidth %.2f%% | %%B %.1f%%\n",
		label, bands.Upper, bands.Middle, bands.Lower, bands.Bandwidth*100, bands.PercentB*100)
}

// formatVolumeLine 构建成交量描述
func formatVolumeLine(label string, stats *VolumeStats) string {
	if stats == nil {
		return ""
	}
	return fmt.Sprintf("- %s: current %.2f vs avg %.2f (ratio %.2fx)\n",
		label, stats.Current, stats.Average, stats.Ratio)
}

// formatADXSnapshotLine 构建多时间框架的ADX概览
func formatADXSnapshotLine(data *Data) string {
	segments := []struct {
		label string
		value float64
	}{
		{"5m", data.CurrentADX14},
		{"15m", data.ADX15m},
		{"1h", data.ADX1h},
		{"4h", data.ADX4h},
	}

	var parts []string
	for _, seg := range segments {
		if seg.value <= 0 {
			parts = append(parts, fmt.Sprintf("%s n/a", seg.label))
			continue
		}
		parts = append(parts, fmt.Sprintf("%s %.2f", seg.label, seg.value))
	}

	if len(parts) == 0 {
		return ""
	}

	return "ADX14 snapshot: " + strings.Join(parts, " | ")
}

// formatFloatSlice 格式化float64切片为字符串（使用动态精度）
func formatFloatSlice(values []float64) string {
	strValues := make([]string, len(values))
	for i, v := range values {
		strValues[i] = formatPriceWithDynamicPrecision(v)
	}
	return "[" + strings.Join(strValues, ", ") + "]"
}

// Normalize 标准化symbol,确保是USDT交易对
func Normalize(symbol string) string {
	symbol = strings.ToUpper(symbol)
	if strings.HasSuffix(symbol, "USDT") {
		return symbol
	}
	return symbol + "USDT"
}

// parseFloat 解析float值
func parseFloat(v interface{}) (float64, error) {
	switch val := v.(type) {
	case string:
		return strconv.ParseFloat(val, 64)
	case float64:
		return val, nil
	case int:
		return float64(val), nil
	case int64:
		return float64(val), nil
	default:
		return 0, fmt.Errorf("unsupported type: %T", v)
	}
}

// BuildDataFromKlines 根据预加载的K线序列构造市场数据快照（用于回测/模拟）。
func BuildDataFromKlines(symbol string, primary []Kline, longer []Kline) (*Data, error) {
	if len(primary) == 0 {
		return nil, fmt.Errorf("primary series is empty")
	}

	symbol = Normalize(symbol)
	current := primary[len(primary)-1]
	currentPrice := current.Close

	intraday := calculateIntradaySeries(primary)

	data := &Data{
		Symbol:            symbol,
		CurrentPrice:      currentPrice,
		CurrentEMA20:      calculateEMA(primary, 20),
		CurrentMACD:       calculateMACD(primary),
		CurrentRSI7:       calculateRSI(primary, 7),
		PriceChange1h:     priceChangeFromSeries(primary, time.Hour),
		PriceChange4h:     priceChangeFromSeries(primary, 4*time.Hour),
		OpenInterest:      &OIData{Latest: 0, Average: 0},
		FundingRate:       0,
		IntradaySeries:    intraday,
		LongerTermContext: nil,
	}

	if intraday != nil {
		data.CurrentADX14 = intraday.LatestADX14
	}

	if len(longer) > 0 {
		data.LongerTermContext = calculateLongerTermData(longer)
		if data.LongerTermContext != nil {
			data.ADX4h = data.LongerTermContext.LatestADX14
		}
	}

	return data, nil
}

func priceChangeFromSeries(series []Kline, duration time.Duration) float64 {
	if len(series) == 0 || duration <= 0 {
		return 0
	}
	last := series[len(series)-1]
	target := last.CloseTime - duration.Milliseconds()
	for i := len(series) - 1; i >= 0; i-- {
		if series[i].CloseTime <= target {
			price := series[i].Close
			if price > 0 {
				return ((last.Close - price) / price) * 100
			}
			break
		}
	}
	return 0
}

// isStaleData detects stale data (consecutive price freeze)
// Fix DOGEUSDT-style issue: consecutive N periods with completely unchanged prices indicate data source anomaly
func isStaleData(klines []Kline, symbol string) bool {
	// Require at least two samples so we can compare price movement
	const minSamples = 2
	if len(klines) < minSamples {
		return false // Insufficient data to determine
	}

	// Detection threshold: 3 consecutive 5-minute periods with unchanged price (15 minutes without fluctuation)
	const stalePriceThreshold = 3
	const priceTolerancePct = 0.0001 // 0.01% fluctuation tolerance (avoid false positives)

	windowSize := stalePriceThreshold
	if len(klines) < windowSize {
		windowSize = len(klines)
	}

	// Take the last windowSize K-lines
	recentKlines := klines[len(klines)-windowSize:]
	firstPrice := recentKlines[0].Close

	// Check if all prices are within tolerance
	for i := 1; i < len(recentKlines); i++ {
		priceDiff := math.Abs(recentKlines[i].Close-firstPrice) / firstPrice
		if priceDiff > priceTolerancePct {
			return false // Price fluctuation exists, data is normal
		}
	}

	// Additional check: MACD and volume
	// If price is unchanged but MACD/volume shows normal fluctuation, it might be a real market situation (extremely low volatility)
	// Check if volume is also 0 (data completely frozen)
	allVolumeZero := true
	for _, k := range recentKlines {
		if k.Volume > 0 {
			allVolumeZero = false
			break
		}
	}

	if allVolumeZero {
		log.Printf("⚠️  %s stale data confirmed: price freeze + zero volume", symbol)
		return true
	}

	// Price frozen but has volume: might be extremely low volatility market, allow but log warning
	log.Printf("⚠️  %s detected extreme price stability (no fluctuation for %d consecutive periods), but volume is normal", symbol, windowSize)
	return false
}
